@mneme-ai/xray 2.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +71 -0
  2. package/dist/battery/age.d.ts +3 -0
  3. package/dist/battery/age.d.ts.map +1 -0
  4. package/dist/battery/age.js +65 -0
  5. package/dist/battery/age.js.map +1 -0
  6. package/dist/battery/busfactor.d.ts +3 -0
  7. package/dist/battery/busfactor.d.ts.map +1 -0
  8. package/dist/battery/busfactor.js +92 -0
  9. package/dist/battery/busfactor.js.map +1 -0
  10. package/dist/battery/complexity.d.ts +3 -0
  11. package/dist/battery/complexity.d.ts.map +1 -0
  12. package/dist/battery/complexity.js +50 -0
  13. package/dist/battery/complexity.js.map +1 -0
  14. package/dist/battery/deps.d.ts +15 -0
  15. package/dist/battery/deps.d.ts.map +1 -0
  16. package/dist/battery/deps.js +107 -0
  17. package/dist/battery/deps.js.map +1 -0
  18. package/dist/battery/hotspots.d.ts +3 -0
  19. package/dist/battery/hotspots.d.ts.map +1 -0
  20. package/dist/battery/hotspots.js +61 -0
  21. package/dist/battery/hotspots.js.map +1 -0
  22. package/dist/battery/secrets.d.ts +3 -0
  23. package/dist/battery/secrets.d.ts.map +1 -0
  24. package/dist/battery/secrets.js +64 -0
  25. package/dist/battery/secrets.js.map +1 -0
  26. package/dist/bin.d.ts +3 -0
  27. package/dist/bin.d.ts.map +1 -0
  28. package/dist/bin.js +76 -0
  29. package/dist/bin.js.map +1 -0
  30. package/dist/clone.d.ts +13 -0
  31. package/dist/clone.d.ts.map +1 -0
  32. package/dist/clone.js +42 -0
  33. package/dist/clone.js.map +1 -0
  34. package/dist/cosmic.d.ts +35 -0
  35. package/dist/cosmic.d.ts.map +1 -0
  36. package/dist/cosmic.js +122 -0
  37. package/dist/cosmic.js.map +1 -0
  38. package/dist/engine.d.ts +8 -0
  39. package/dist/engine.d.ts.map +1 -0
  40. package/dist/engine.js +138 -0
  41. package/dist/engine.js.map +1 -0
  42. package/dist/gauntlet.d.ts +9 -0
  43. package/dist/gauntlet.d.ts.map +1 -0
  44. package/dist/gauntlet.js +47 -0
  45. package/dist/gauntlet.js.map +1 -0
  46. package/dist/index.d.ts +21 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +21 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/privacy.d.ts +12 -0
  51. package/dist/privacy.d.ts.map +1 -0
  52. package/dist/privacy.js +43 -0
  53. package/dist/privacy.js.map +1 -0
  54. package/dist/publish.d.ts +9 -0
  55. package/dist/publish.d.ts.map +1 -0
  56. package/dist/publish.js +28 -0
  57. package/dist/publish.js.map +1 -0
  58. package/dist/server.d.ts +29 -0
  59. package/dist/server.d.ts.map +1 -0
  60. package/dist/server.js +482 -0
  61. package/dist/server.js.map +1 -0
  62. package/dist/sign.d.ts +7 -0
  63. package/dist/sign.d.ts.map +1 -0
  64. package/dist/sign.js +33 -0
  65. package/dist/sign.js.map +1 -0
  66. package/dist/types.d.ts +148 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +16 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/util.d.ts +21 -0
  71. package/dist/util.d.ts.map +1 -0
  72. package/dist/util.js +111 -0
  73. package/dist/util.js.map +1 -0
  74. package/package.json +55 -0
  75. package/public/card.js +45 -0
  76. package/public/cosmic.html +74 -0
  77. package/public/favicon.svg +1 -0
  78. package/public/index.html +294 -0
  79. package/public/report.html +76 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @mneme-ai/xray — Repo X-Ray
2
+
3
+ **Live: https://xray.mneme-ai.space** — paste any public repo, get a signed health X-Ray in seconds (no install).
4
+
5
+ A **signed, raw-free, deterministic X-Ray of any repo.** Paste a public git URL (or point the CLI at a local path) and get a graded report: dependency mortality, secret leaks, bus factor, vitality, and complexity hotspots.
6
+
7
+ Three guarantees, by construction:
8
+
9
+ 1. **Accurate.** Every number comes from a deterministic `@mneme-ai/core` analyzer (git history · AST outline · npm registry metadata · regex secret scan). **No LLM guesses anything** — the same repo at the same commit always produces the same report.
10
+ 2. **Private.** Public repos are shallow-cloned to a temp dir, analysed, and **deleted**. The report is **raw-free** — it carries only metrics, counts, line numbers, symbol names, and hashes, never a line of source. `xrayLeaksRaw()` proves it (gauntlet-enforced). Private repos never leave your machine: run the CLI locally.
11
+ 3. **Verifiable.** The whole report is sealed with an **Ed25519 NOTARY receipt** any third party verifies **offline** with the embedded public key — no Mneme instance, no network, no shared secret.
12
+
13
+ ## CLI (local / private repos — nothing uploaded)
14
+
15
+ ```bash
16
+ npx @mneme-ai/xray . # local folder (git OR not) — analysed in place
17
+ npx @mneme-ai/xray https://github.com/owner/repo # public repo
18
+ npx @mneme-ai/xray ./private-repo --publish \
19
+ --server https://xray.mneme-ai.space --token YOUR_KEY # send ONLY the signed, raw-free report
20
+ ```
21
+
22
+ The CLI works on any local folder — including one that isn't a git repo (git
23
+ signals are simply skipped). Source never leaves your machine; `--publish` sends
24
+ only the raw-free, signed report to your private dashboard.
25
+
26
+ ## Embed a badge
27
+
28
+ A signed, self-updating grade for any README — links back to the full report:
29
+
30
+ ```md
31
+ [![Mneme X-Ray](https://xray.mneme-ai.space/badge/github/owner/repo.svg)](https://xray.mneme-ai.space/r/<fingerprint>)
32
+ ```
33
+
34
+ ## Server (the "Lighthouse")
35
+
36
+ ```bash
37
+ npm run -w @mneme-ai/xray serve # http://0.0.0.0:8787
38
+ ```
39
+
40
+ | Endpoint | |
41
+ |---|---|
42
+ | `POST /api/xray` `{gitUrl}` | clone public repo → battery → raw-free gate → NOTARY seal → report |
43
+ | `POST /api/verify` `{signed}` | verify a report's receipt offline |
44
+ | `GET /api/board` | recent public X-Rays |
45
+ | `GET /api/health` | liveness |
46
+ | `GET /` | the clean white UI |
47
+
48
+ Env: `PORT` (8787) · `HOST` (0.0.0.0) · `XRAY_DATA_DIR` (./.xray-data).
49
+
50
+ ## Deploy 24/7 on DigitalOcean
51
+
52
+ **One click (no command line)** — authorize GitHub, get a public `…ondigitalocean.app` URL:
53
+
54
+ [![Deploy to DigitalOcean](https://www.deploy.do/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/patsa2561-art/mneme-ai/tree/main)
55
+
56
+ **App Platform (CLI):** `doctl apps create --spec packages/xray/.do/app.yaml` (auto-deploys on push to `main`).
57
+
58
+ **Droplet (durable board):**
59
+ ```bash
60
+ docker build -f packages/xray/Dockerfile -t mneme-xray .
61
+ docker run -d --restart=always -p 80:8787 -v /srv/xray-data:/data mneme-xray
62
+ ```
63
+
64
+ ## Architecture — Lighthouse + Reactor
65
+
66
+ ```
67
+ [ your machine: code ] --mneme-xray ./path--> raw-free signed report (private repos: never leaves)
68
+ [ public git URL ] --POST /api/xray-----> Lighthouse (DigitalOcean): clone → analyse → delete → sign
69
+ ```
70
+
71
+ The server (Lighthouse) only ever holds raw-free, signed reports. The accurate engine (Reactor) runs the same `@mneme-ai/core` functions whether local or in the cloud.
@@ -0,0 +1,3 @@
1
+ import type { AgeBlock } from "../types.js";
2
+ export declare function analyzeAge(repoPath: string, now: number): AgeBlock;
3
+ //# sourceMappingURL=age.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age.d.ts","sourceRoot":"","sources":["../../src/battery/age.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAoB5C,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAwClE"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Age / vitality signal — computed directly from `git log` for accuracy.
3
+ *
4
+ * NOTE: we deliberately do NOT use funeral.collectEulogyStats here — as of
5
+ * v2.150 that helper reports bornAt==diedAt (lifespan 0 days) on this repo,
6
+ * a core bug to fix separately. Reading the first/last commit dates ourselves
7
+ * is exact and dependency-free.
8
+ */
9
+ import { git, isGitRepo } from "../util.js";
10
+ const DAY_MS = 1000 * 60 * 60 * 24;
11
+ const NOT_GIT = {
12
+ bornAt: "", lastCommitAt: "", lifespan: "n/a", lifespanDays: 0,
13
+ totalCommits: 0, totalAuthors: 0, dormant: false, vitality: "active",
14
+ note: "Not a git repository — history/vitality signals unavailable (deps, secrets, complexity still analysed).",
15
+ };
16
+ function humanSpan(days) {
17
+ if (days < 1)
18
+ return "less than a day";
19
+ const years = Math.floor(days / 365);
20
+ const months = Math.floor((days % 365) / 30.44);
21
+ const parts = [];
22
+ if (years)
23
+ parts.push(`${years} year${years > 1 ? "s" : ""}`);
24
+ if (months)
25
+ parts.push(`${months} month${months > 1 ? "s" : ""}`);
26
+ if (!years && !months)
27
+ parts.push(`${Math.round(days)} day${Math.round(days) === 1 ? "" : "s"}`);
28
+ return parts.join(", ");
29
+ }
30
+ export function analyzeAge(repoPath, now) {
31
+ if (!isGitRepo(repoPath))
32
+ return NOT_GIT;
33
+ const first = git(repoPath, ["log", "--reverse", "--format=%aI", "--max-parents=0"]).split("\n")[0]?.trim()
34
+ || git(repoPath, ["log", "--reverse", "--format=%aI"]).split("\n")[0]?.trim();
35
+ const last = git(repoPath, ["log", "-1", "--format=%aI"]).trim();
36
+ const totalCommits = parseInt(git(repoPath, ["rev-list", "--count", "HEAD"]).trim() || "0", 10);
37
+ const authors = new Set(git(repoPath, ["log", "--format=%ae"]).split("\n").map((s) => s.trim()).filter(Boolean)).size;
38
+ if (!first || !last || totalCommits === 0) {
39
+ return {
40
+ bornAt: "", lastCommitAt: "", lifespan: "unknown", lifespanDays: 0,
41
+ totalCommits: 0, totalAuthors: 0, dormant: true, vitality: "dormant",
42
+ note: "Could not read git history.",
43
+ };
44
+ }
45
+ const bornMs = Date.parse(first);
46
+ const lastMs = Date.parse(last);
47
+ const lifespanDays = Number.isFinite(bornMs) && Number.isFinite(lastMs) ? Math.max(0, (lastMs - bornMs) / DAY_MS) : 0;
48
+ const monthsSince = Number.isFinite(lastMs) ? (now - lastMs) / (DAY_MS * 30.44) : 999;
49
+ const archived = false; // we cannot know archive status from clone alone; report only what is provable
50
+ const vitality = monthsSince >= 12 ? "dormant" : monthsSince >= 4 ? "slowing" : "active";
51
+ return {
52
+ bornAt: first,
53
+ lastCommitAt: last,
54
+ lifespan: humanSpan(lifespanDays),
55
+ lifespanDays: Math.round(lifespanDays),
56
+ totalCommits,
57
+ totalAuthors: authors,
58
+ dormant: vitality === "dormant",
59
+ vitality: archived ? "archived" : vitality,
60
+ note: vitality === "active" ? "Actively maintained — recent commit activity."
61
+ : vitality === "slowing" ? "Commit cadence is slowing (no commit in 4+ months)."
62
+ : "Dormant — no commit in 12+ months.",
63
+ };
64
+ }
65
+ //# sourceMappingURL=age.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age.js","sourceRoot":"","sources":["../../src/battery/age.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { BusFactorBlock } from "../types.js";
2
+ export declare function analyzeBusFactor(repoPath: string): BusFactorBlock;
3
+ //# sourceMappingURL=busfactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"busfactor.d.ts","sourceRoot":"","sources":["../../src/battery/busfactor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAsEjE"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Bus-factor signal — knowledge concentration from git authorship.
3
+ * Pure computation over `git log` (deterministic given repo state). For each
4
+ * file: the share of its commits held by its single top author. A file with
5
+ * one dominant author is fragile (knowledge dies if that person leaves).
6
+ */
7
+ import { git, isGitRepo } from "../util.js";
8
+ const SKIP_DIR = /(^|\/)(node_modules|\.git|dist|build|vendor|\.next|coverage)(\/|$)/;
9
+ const DOMINANCE = 0.8; // a file is "single-owner" when its top author holds >= 80% of its commits
10
+ export function analyzeBusFactor(repoPath) {
11
+ if (!isGitRepo(repoPath))
12
+ return emptyBlock("Not a git repository — authorship/bus-factor signals unavailable.");
13
+ // One line per (commit, file): "<authorEmail>\t<file>". --no-renames keeps paths stable.
14
+ const raw = git(repoPath, [
15
+ "log", "--no-merges", "--pretty=format:C%H%x09%ae", "--name-only", "-n", "4000",
16
+ ]);
17
+ if (!raw.trim()) {
18
+ return emptyBlock("No commit history available.");
19
+ }
20
+ const fileAuthors = new Map(); // file -> author -> commits
21
+ const authorCommits = new Map(); // author -> total commits
22
+ const allAuthors = new Set();
23
+ let curAuthor = "";
24
+ let totalCommits = 0;
25
+ for (const line of raw.split("\n")) {
26
+ if (line.startsWith("C")) {
27
+ const tab = line.indexOf("\t");
28
+ curAuthor = tab >= 0 ? line.slice(tab + 1).trim() : "";
29
+ if (curAuthor) {
30
+ allAuthors.add(curAuthor);
31
+ authorCommits.set(curAuthor, (authorCommits.get(curAuthor) ?? 0) + 1);
32
+ totalCommits++;
33
+ }
34
+ continue;
35
+ }
36
+ const file = line.trim();
37
+ if (!file || SKIP_DIR.test(file) || !curAuthor)
38
+ continue;
39
+ let m = fileAuthors.get(file);
40
+ if (!m) {
41
+ m = new Map();
42
+ fileAuthors.set(file, m);
43
+ }
44
+ m.set(curAuthor, (m.get(curAuthor) ?? 0) + 1);
45
+ }
46
+ if (totalCommits === 0)
47
+ return emptyBlock("No authored commits found.");
48
+ let singleOwner = 0;
49
+ const fragile = [];
50
+ for (const [file, m] of fileAuthors) {
51
+ let top = 0, sum = 0;
52
+ for (const c of m.values()) {
53
+ sum += c;
54
+ if (c > top)
55
+ top = c;
56
+ }
57
+ if (sum < 3)
58
+ continue; // ignore barely-touched files
59
+ const share = top / sum;
60
+ if (share >= DOMINANCE) {
61
+ singleOwner++;
62
+ fragile.push({ file, topAuthorShare: Math.round(share * 100) / 100, commits: sum });
63
+ }
64
+ }
65
+ fragile.sort((a, b) => b.commits - a.commits);
66
+ const consideredFiles = [...fileAuthors.values()].filter((m) => [...m.values()].reduce((s, c) => s + c, 0) >= 3).length;
67
+ const topContributor = Math.max(0, ...authorCommits.values());
68
+ const topShare = totalCommits > 0 ? topContributor / totalCommits : 0;
69
+ // bus factor ≈ how many top authors it takes to cover 50% of commits.
70
+ const sorted = [...authorCommits.values()].sort((a, b) => b - a);
71
+ let cum = 0, busFactor = 0;
72
+ for (const c of sorted) {
73
+ cum += c;
74
+ busFactor++;
75
+ if (cum >= totalCommits * 0.5)
76
+ break;
77
+ }
78
+ return {
79
+ authors: allAuthors.size,
80
+ singleOwnerFilePct: consideredFiles > 0 ? Math.round((singleOwner / consideredFiles) * 1000) / 10 : 0,
81
+ fragileFiles: fragile.slice(0, 15),
82
+ topContributorShare: Math.round(topShare * 1000) / 10,
83
+ busFactor,
84
+ note: busFactor <= 1
85
+ ? "Bus factor 1 — a single person dominates this codebase. High key-person risk."
86
+ : `${allAuthors.size} authors; ${singleOwner} files are single-owner (>=80% one author).`,
87
+ };
88
+ }
89
+ function emptyBlock(note) {
90
+ return { authors: 0, singleOwnerFilePct: 0, fragileFiles: [], topContributorShare: 0, busFactor: 0, note };
91
+ }
92
+ //# sourceMappingURL=busfactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"busfactor.js","sourceRoot":"","sources":["../../src/battery/busfactor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ComplexityBlock } from "../types.js";
2
+ export declare function analyzeComplexity(repoPath: string, maxFiles: number): ComplexityBlock;
3
+ //# sourceMappingURL=complexity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complexity.d.ts","sourceRoot":"","sources":["../../src/battery/complexity.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CAuCrF"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Complexity signal — wraps the real `outline.extractOutline` (deterministic
3
+ * AST-structural scan). Reports symbol counts, the longest functions (refactor
4
+ * hotspots), and max nesting depth. Symbol NAMES + signatures are structural
5
+ * metadata, not source bodies — bodies are never read into the report.
6
+ */
7
+ import { outline } from "@mneme-ai/core";
8
+ import { listTextFiles, readText } from "../util.js";
9
+ const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|c|h|cpp|cc)$/i;
10
+ export function analyzeComplexity(repoPath, maxFiles) {
11
+ const { files } = listTextFiles(repoPath, maxFiles);
12
+ const hotspots = [];
13
+ let totalSymbols = 0;
14
+ let analysed = 0;
15
+ let maxDepth = 0;
16
+ for (const f of files) {
17
+ if (!CODE_EXT.test(f.rel))
18
+ continue;
19
+ const src = readText(f.abs);
20
+ if (!src)
21
+ continue;
22
+ let o;
23
+ try {
24
+ o = outline.extractOutline(src, { path: f.rel });
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ analysed++;
30
+ totalSymbols += o.symbolCount;
31
+ for (const sym of o.symbols) {
32
+ if (sym.depth > maxDepth)
33
+ maxDepth = sym.depth;
34
+ if (sym.kind === "function" || sym.kind === "method") {
35
+ hotspots.push({ file: f.rel, symbol: sym.name, bodyLines: sym.bodyLines, startLine: sym.startLine });
36
+ }
37
+ }
38
+ }
39
+ hotspots.sort((a, b) => b.bodyLines - a.bodyLines);
40
+ return {
41
+ filesAnalysed: analysed,
42
+ totalSymbols,
43
+ hotspots: hotspots.slice(0, 15),
44
+ maxDepth,
45
+ note: hotspots.length === 0
46
+ ? "No code symbols extracted."
47
+ : `Largest function: ${hotspots[0].symbol} (${hotspots[0].bodyLines} lines). Long functions are the refactor hotspots.`,
48
+ };
49
+ }
50
+ //# sourceMappingURL=complexity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complexity.js","sourceRoot":"","sources":["../../src/battery/complexity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,QAAQ,GAAG,sDAAsD,CAAC;AAExE,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAgB;IAClE,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAgC,EAAE,CAAC;IACjD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,CAA4C,CAAC;QACjD,IAAI,CAAC;YACH,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,QAAQ,EAAE,CAAC;QACX,YAAY,IAAI,CAAC,CAAC,WAAW,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,KAAK,GAAG,QAAQ;gBAAE,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC;YAC/C,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;YACvG,CAAC;QACH,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAEnD,OAAO;QACL,aAAa,EAAE,QAAQ;QACvB,YAAY;QACZ,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QAC/B,QAAQ;QACR,IAAI,EACF,QAAQ,CAAC,MAAM,KAAK,CAAC;YACnB,CAAC,CAAC,4BAA4B;YAC9B,CAAC,CAAC,qBAAqB,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,oDAAoD;KAC5H,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dependency-mortality signal — wraps the real `depMortality.predictMortality`.
3
+ * Reads the repo's package.json, fetches public npm registry metadata per dep
4
+ * (factual, not an LLM guess), and scores each. Metadata fetch is injectable
5
+ * for deterministic tests; the registry is the source of truth otherwise.
6
+ */
7
+ import { depMortality } from "@mneme-ai/core";
8
+ import type { DepsBlock } from "../types.js";
9
+ type NpmMeta = Parameters<typeof depMortality.predictMortality>[0];
10
+ export type MetaFetcher = (pkg: string, now: number) => Promise<NpmMeta | null>;
11
+ /** Default: fetch the public npm registry document and derive mortality inputs. */
12
+ export declare const defaultFetcher: MetaFetcher;
13
+ export declare function analyzeDeps(repoPath: string, now: number, fetcher?: MetaFetcher): Promise<DepsBlock>;
14
+ export {};
15
+ //# sourceMappingURL=deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/battery/deps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAIhF,mFAAmF;AACnF,eAAO,MAAM,cAAc,EAAE,WA4C5B,CAAC;AAcF,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAA4B,GAAG,OAAO,CAAC,SAAS,CAAC,CAsC1H"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Dependency-mortality signal — wraps the real `depMortality.predictMortality`.
3
+ * Reads the repo's package.json, fetches public npm registry metadata per dep
4
+ * (factual, not an LLM guess), and scores each. Metadata fetch is injectable
5
+ * for deterministic tests; the registry is the source of truth otherwise.
6
+ */
7
+ import { depMortality } from "@mneme-ai/core";
8
+ import { readText } from "../util.js";
9
+ import { join } from "node:path";
10
+ const MONTH_MS = 1000 * 60 * 60 * 24 * 30.44;
11
+ /** Default: fetch the public npm registry document and derive mortality inputs. */
12
+ export const defaultFetcher = async (pkg, now) => {
13
+ try {
14
+ const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg).replace("%40", "@")}`, {
15
+ headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
16
+ });
17
+ if (!res.ok)
18
+ return null;
19
+ const doc = (await res.json());
20
+ const latest = doc["dist-tags"]?.latest;
21
+ const time = doc.time ?? {};
22
+ const latestAt = latest ? time[latest] : undefined;
23
+ const monthsSinceLatest = latestAt ? (now - Date.parse(latestAt)) / MONTH_MS : undefined;
24
+ // months since last feature (non-patch) release: walk versions newest→oldest
25
+ const versions = Object.keys(doc.versions ?? {});
26
+ let monthsSinceFeatureRelease;
27
+ if (latest) {
28
+ const [lMaj, lMin] = latest.split(".").map((n) => parseInt(n, 10));
29
+ let bestAt = 0;
30
+ for (const v of versions) {
31
+ const [maj, min, pat] = v.split(".").map((n) => parseInt(n, 10));
32
+ if (pat === 0 && (maj !== lMaj || min !== lMin || v === latest)) {
33
+ const t = Date.parse(time[v] ?? "");
34
+ if (Number.isFinite(t) && t > bestAt)
35
+ bestAt = t;
36
+ }
37
+ }
38
+ if (bestAt > 0)
39
+ monthsSinceFeatureRelease = (now - bestAt) / MONTH_MS;
40
+ }
41
+ const deprecated = !!(latest && doc.versions?.[latest]?.deprecated);
42
+ return {
43
+ name: pkg,
44
+ latestPublishedAt: latestAt,
45
+ monthsSinceLatest,
46
+ monthsSinceFeatureRelease,
47
+ deprecated,
48
+ maintainerCount: Array.isArray(doc.maintainers) ? doc.maintainers.length : undefined,
49
+ };
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ };
55
+ function depNames(repoPath) {
56
+ const raw = readText(join(repoPath, "package.json"));
57
+ if (!raw)
58
+ return [];
59
+ try {
60
+ const pkg = JSON.parse(raw);
61
+ const names = new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
62
+ return [...names];
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ }
68
+ export async function analyzeDeps(repoPath, now, fetcher = defaultFetcher) {
69
+ const names = depNames(repoPath);
70
+ const byBand = { thriving: 0, healthy: 0, watch: 0, moribund: 0, dead: 0 };
71
+ const atRisk = [];
72
+ if (names.length === 0) {
73
+ return { total: 0, byBand, atRisk, partial: false, note: "No package.json dependencies found (non-npm repo or no deps)." };
74
+ }
75
+ let partial = false;
76
+ // bounded concurrency to be a polite registry citizen
77
+ const LIMIT = 8;
78
+ for (let i = 0; i < names.length; i += LIMIT) {
79
+ const chunk = names.slice(i, i + LIMIT);
80
+ const metas = await Promise.all(chunk.map((n) => fetcher(n, now).catch(() => null)));
81
+ for (const meta of metas) {
82
+ if (!meta) {
83
+ partial = true;
84
+ continue;
85
+ }
86
+ const r = depMortality.predictMortality(meta);
87
+ byBand[r.band]++;
88
+ if (r.band === "watch" || r.band === "moribund" || r.band === "dead") {
89
+ atRisk.push({ name: r.package, band: r.band, probability18mo: Math.round(r.probability18mo * 100) / 100, successor: meta.knownSubstitute ?? null });
90
+ }
91
+ }
92
+ }
93
+ atRisk.sort((a, b) => b.probability18mo - a.probability18mo);
94
+ const danger = byBand.moribund + byBand.dead;
95
+ return {
96
+ total: names.length,
97
+ byBand,
98
+ atRisk: atRisk.slice(0, 20),
99
+ partial,
100
+ note: danger > 0
101
+ ? `${danger} depend<x>${danger === 1 ? "y is" : "ies are"}</x> dying (moribund/dead). Plan replacements.`.replace(/<x>|<\/x>/g, "")
102
+ : partial
103
+ ? "Some packages could not be reached on the npm registry (counted as unknown)."
104
+ : "No dying dependencies detected.",
105
+ };
106
+ }
107
+ //# sourceMappingURL=deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/battery/deps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { HotspotsBlock } from "../types.js";
2
+ export declare function analyzeHotspots(repoPath: string, now: number, windowDays?: number): HotspotsBlock;
3
+ //# sourceMappingURL=hotspots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspots.d.ts","sourceRoot":"","sources":["../../src/battery/hotspots.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,aAAa,CAqC9F"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * HOTSPOTS — behavioral code analysis (the research-grounded signal).
3
+ *
4
+ * Defects and maintenance cost don't spread evenly: they concentrate in files
5
+ * that are BOTH changed often AND large/complex. This is well established —
6
+ * code churn predicts defects (Nagappan & Ball, ICSE'05), and "hotspots" =
7
+ * change-frequency × complexity surface the highest-ROI refactoring targets
8
+ * (Tornhill, *Your Code as a Crime Scene*; D'Ambros & Lanza, evolutionary
9
+ * measures). We compute it 100% deterministically:
10
+ *
11
+ * change-frequency ← `git log --name-only` over a window (no blob fetch —
12
+ * works on a blobless clone; uses commit/tree metadata)
13
+ * complexity proxy ← current lines-of-code of the file (HEAD blob)
14
+ * hotspot score ← changeCount × loc, ranked
15
+ *
16
+ * The output answers "where do I refactor first?" — a question no secret scanner
17
+ * or dependency checker answers, and one a CTO actually pays for.
18
+ */
19
+ import { git, readText, isGitRepo } from "../util.js";
20
+ import { join } from "node:path";
21
+ const SKIP_DIR = /(^|\/)(node_modules|\.git|dist|build|vendor|\.next|coverage|__pycache__|\.venv|target)(\/|$)/;
22
+ const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|c|h|cpp|cc|rb|php|cs|kt|swift|scala|vue|svelte)$/i;
23
+ export function analyzeHotspots(repoPath, now, windowDays = 365) {
24
+ if (!isGitRepo(repoPath)) {
25
+ return { windowDays, filesConsidered: 0, hotspots: [], note: "Not a git repository — hotspot history unavailable." };
26
+ }
27
+ const since = new Date(now - windowDays * 86_400_000).toISOString();
28
+ // empty pretty format → output is just the changed file paths, one per line,
29
+ // per commit. Counting occurrences = how many commits touched each file.
30
+ const raw = git(repoPath, ["log", "--since", since, "--no-merges", "--name-only", "--pretty=format:"]);
31
+ if (!raw.trim())
32
+ return { windowDays, filesConsidered: 0, hotspots: [], note: "No commit activity in the window." };
33
+ const changes = new Map();
34
+ for (const line of raw.split("\n")) {
35
+ const f = line.trim();
36
+ if (!f || SKIP_DIR.test(f) || !CODE_EXT.test(f))
37
+ continue;
38
+ changes.set(f, (changes.get(f) ?? 0) + 1);
39
+ }
40
+ if (changes.size === 0)
41
+ return { windowDays, filesConsidered: 0, hotspots: [], note: "No source-file changes in the window." };
42
+ // join change-frequency with current size (complexity proxy). Only read LOC
43
+ // for the most-changed files (bounded work).
44
+ const ranked = [...changes.entries()].sort((a, b) => b[1] - a[1]).slice(0, 80);
45
+ const rows = ranked.map(([file, changeCount]) => {
46
+ const txt = readText(join(repoPath, file));
47
+ const loc = txt ? txt.split("\n").length : 0;
48
+ return { file, changes: changeCount, loc, score: changeCount * loc };
49
+ }).filter((r) => r.loc > 0);
50
+ rows.sort((a, b) => b.score - a.score);
51
+ const top = rows[0];
52
+ return {
53
+ windowDays,
54
+ filesConsidered: changes.size,
55
+ hotspots: rows.slice(0, 15),
56
+ note: top
57
+ ? `Hotspot: ${top.file} — changed ${top.changes}× and ${top.loc} lines. High churn × size = where defects and refactoring ROI concentrate (behavioral code analysis).`
58
+ : "No hotspots surfaced.",
59
+ };
60
+ }
61
+ //# sourceMappingURL=hotspots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspots.js","sourceRoot":"","sources":["../../src/battery/hotspots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SecretsBlock } from "../types.js";
2
+ export declare function scanSecrets(repoPath: string, maxFiles: number): SecretsBlock;
3
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/battery/secrets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAyC5E"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Secret-leak signal — wraps the real `egress.scanEgress` (deterministic regex).
3
+ * Reports kind + file + line ONLY; the secret value is never read into the report.
4
+ *
5
+ * PRECISION: a credential pattern in a TEST / FIXTURE / DOC / EXAMPLE file is
6
+ * almost always intentional sample data (especially in a security repo whose job
7
+ * is to detect secrets), NOT a real leak. We classify every hit by path and count
8
+ * ONLY production-code hits toward the headline + grade; test/fixture/doc hits are
9
+ * reported separately as `excludedTestHits` so the signal means "real leak risk",
10
+ * not "matched a pattern somewhere". Entropy detection stays OFF (source code is
11
+ * full of high-entropy tokens → noise).
12
+ */
13
+ import { egress } from "@mneme-ai/core";
14
+ import { listTextFiles, readText } from "../util.js";
15
+ const NON_PROD = /(\.test\.|\.spec\.|[._-]fixtures?\b|__tests__|__fixtures__|__mocks__|(^|\/)(tests?|spec|fixtures?|examples?|samples?|mocks?|docs?|e2e|benchmarks?|bench)\/|\.stories\.|\.md$|\.mdx$|\.snap$|\.lock$|fixture)/i;
16
+ const isProd = (rel) => !NON_PROD.test(rel);
17
+ export function scanSecrets(repoPath, maxFiles) {
18
+ const { files } = listTextFiles(repoPath, maxFiles);
19
+ const byKind = {};
20
+ const hits = [];
21
+ let total = 0, excluded = 0, scanned = 0;
22
+ let worst = "ALLOW";
23
+ for (const f of files) {
24
+ const text = readText(f.abs);
25
+ if (!text)
26
+ continue;
27
+ scanned++;
28
+ const prod = isProd(f.rel);
29
+ const lines = text.split("\n");
30
+ for (let i = 0; i < lines.length; i++) {
31
+ const r = egress.scanEgress({ payload: lines[i], entropy: { enabled: false } });
32
+ if (r.findings.length === 0)
33
+ continue;
34
+ const n = r.findings.reduce((s, fnd) => s + fnd.count, 0);
35
+ if (!prod) {
36
+ excluded += n;
37
+ continue;
38
+ } // test/fixture/doc → not a leak
39
+ if (r.verdict === "BLOCK")
40
+ worst = "BLOCK";
41
+ else if (r.verdict === "REDACT" && worst === "ALLOW")
42
+ worst = "REDACT";
43
+ for (const finding of r.findings) {
44
+ byKind[finding.kind] = (byKind[finding.kind] ?? 0) + finding.count;
45
+ total += finding.count;
46
+ if (hits.length < 50)
47
+ hits.push({ kind: finding.kind, file: f.rel, line: i + 1 });
48
+ }
49
+ }
50
+ }
51
+ const tail = excluded > 0 ? ` (${excluded} more in test/fixture/doc files — excluded as intentional sample data)` : "";
52
+ return {
53
+ filesScanned: scanned,
54
+ totalFindings: total,
55
+ excludedTestHits: excluded,
56
+ byKind,
57
+ hits,
58
+ worstVerdict: worst,
59
+ note: total === 0
60
+ ? `No credential patterns in production code${tail}.`
61
+ : `${total} credential-pattern match(es) in production code — review${tail}. Kind+file+line only; the value is never stored.`,
62
+ };
63
+ }
64
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/battery/secrets.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}